/*
 * @(#)Util.java   0.3-3 06/05/2001
 *
 *  This file is part of the HTTPClient package
 *  Copyright (C) 1996-2001 Ronald Tschal�r
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA 02111-1307, USA
 *
 *  For questions, suggestions, bug-reports, enhancement-requests etc.
 *  I may be contacted at:
 *
 *  ronald@innovation.ch
 *
 *  The HTTPClient's home page is located at:
 *
 *  http://www.innovation.ch/java/HTTPClient/ 
 *
 */

package org.everrest.http.client;

import java.lang.reflect.Array;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.BitSet;
import java.util.Date;
import java.util.Hashtable;
import java.util.Locale;
import java.util.SimpleTimeZone;
import java.util.StringTokenizer;
import java.util.Vector;

/**
 * This class holds various utility methods.
 *
 * @author Ronald Tschal�r
 * @version 0.3-3 06/05/2001
 */
public class Util {
    private static final BitSet Separators = new BitSet(128);

    private static final BitSet TokenChar = new BitSet(128);

    private static final BitSet UnsafeChar = new BitSet(128);

    private static DateFormat http_format;

    private static DateFormat parse_1123;

    private static DateFormat parse_850;

    private static DateFormat parse_asctime;

    private static final Object http_format_lock = new Object();

    private static final Object http_parse_lock = new Object();

    static {
        // rfc-2616 tspecial
        Separators.set('(');
        Separators.set(')');
        Separators.set('<');
        Separators.set('>');
        Separators.set('@');
        Separators.set(',');
        Separators.set(';');
        Separators.set(':');
        Separators.set('\\');
        Separators.set('"');
        Separators.set('/');
        Separators.set('[');
        Separators.set(']');
        Separators.set('?');
        Separators.set('=');
        Separators.set('{');
        Separators.set('}');
        Separators.set(' ');
        Separators.set('\t');

        // rfc-2616 token
        for (int ch = 32; ch < 127; ch++)
            TokenChar.set(ch);
        TokenChar.xor(Separators);

        // rfc-1738 unsafe characters, including CTL and SP, and excluding
        // "#" and "%"
        for (int ch = 0; ch < 32; ch++)
            UnsafeChar.set(ch);
        UnsafeChar.set(' ');
        UnsafeChar.set('<');
        UnsafeChar.set('>');
        UnsafeChar.set('"');
        UnsafeChar.set('{');
        UnsafeChar.set('}');
        UnsafeChar.set('|');
        UnsafeChar.set('\\');
        UnsafeChar.set('^');
        UnsafeChar.set('~');
        UnsafeChar.set('[');
        UnsafeChar.set(']');
        UnsafeChar.set('`');
        UnsafeChar.set(127);

        // rfc-1123 date format (restricted to GMT, as per rfc-2616)
      /*
       * This initialization has been moved to httpDate() because it takes an
       * awfully long time and is often not needed http_format = new
       * SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
       * http_format.setTimeZone(new SimpleTimeZone(0, "GMT"));
       */
    }

    // Constructors

    /** This class isn't meant to be instantiated. */
    private Util() {
    }

    // Methods

    final static Object[] resizeArray(Object[] src, int new_size) {
        Class compClass = src.getClass().getComponentType();
        Object tmp[] = (Object[])Array.newInstance(compClass, new_size);
        System.arraycopy(src, 0, tmp, 0, (src.length < new_size ? src.length : new_size));
        return tmp;
    }

    final static NVPair[] resizeArray(NVPair[] src, int new_size) {
        NVPair tmp[] = new NVPair[new_size];
        System.arraycopy(src, 0, tmp, 0, (src.length < new_size ? src.length : new_size));
        return tmp;
    }

    final static AuthorizationInfo[] resizeArray(AuthorizationInfo[] src, int new_size) {
        AuthorizationInfo tmp[] = new AuthorizationInfo[new_size];
        System.arraycopy(src, 0, tmp, 0, (src.length < new_size ? src.length : new_size));
        return tmp;
    }

    final static Cookie[] resizeArray(Cookie[] src, int new_size) {
        Cookie tmp[] = new Cookie[new_size];
        System.arraycopy(src, 0, tmp, 0, (src.length < new_size ? src.length : new_size));
        return tmp;
    }

    final static String[] resizeArray(String[] src, int new_size) {
        String tmp[] = new String[new_size];
        System.arraycopy(src, 0, tmp, 0, (src.length < new_size ? src.length : new_size));
        return tmp;
    }

    final static boolean[] resizeArray(boolean[] src, int new_size) {
        boolean tmp[] = new boolean[new_size];
        System.arraycopy(src, 0, tmp, 0, (src.length < new_size ? src.length : new_size));
        return tmp;
    }

    final static byte[] resizeArray(byte[] src, int new_size) {
        byte tmp[] = new byte[new_size];
        System.arraycopy(src, 0, tmp, 0, (src.length < new_size ? src.length : new_size));
        return tmp;
    }

    final static char[] resizeArray(char[] src, int new_size) {
        char tmp[] = new char[new_size];
        System.arraycopy(src, 0, tmp, 0, (src.length < new_size ? src.length : new_size));
        return tmp;
    }

    final static int[] resizeArray(int[] src, int new_size) {
        int tmp[] = new int[new_size];
        System.arraycopy(src, 0, tmp, 0, (src.length < new_size ? src.length : new_size));
        return tmp;
    }

    /** Split a property into an array of Strings, using "|" as the separator. */
    static String[] splitProperty(String prop) {
        if (prop == null)
            return new String[0];

        StringTokenizer tok = new StringTokenizer(prop, "|");
        String[] list = new String[tok.countTokens()];
        for (int idx = 0; idx < list.length; idx++)
            list[idx] = tok.nextToken().trim();

        return list;
    }

    /**
     * Helper method for context lists used by modules. Returns the list
     * associated with the context if it exists; otherwise it creates a new list
     * and adds it to the context list.
     *
     * @param cntxt_list
     *         the list of lists indexed by context
     * @param cntxt
     *         the context
     */
    final static Hashtable getList(Hashtable cntxt_list, Object cntxt) {
        synchronized (cntxt_list) {
            Hashtable list = (Hashtable)cntxt_list.get(cntxt);
            if (list == null) {
                list = new Hashtable();
                cntxt_list.put(cntxt, list);
            }

            return list;
        }
    }

    /**
     * Creates an array of distances to speed up the search in findStr(). The
     * returned array should be passed as the second argument to findStr().
     *
     * @param search
     *         the search string (same as the first argument to findStr()).
     * @return an array of distances (to be passed as the second argument to
     *         findStr()).
     */
    final static int[] compile_search(byte[] search) {
        int[] cmp = {0, 1, 0, 1, 0, 1};
        int end;

        for (int idx = 0; idx < search.length; idx++) {
            for (end = idx + 1; end < search.length; end++) {
                if (search[idx] == search[end])
                    break;
            }
            if (end < search.length) {
                if ((end - idx) > cmp[1]) {
                    cmp[4] = cmp[2];
                    cmp[5] = cmp[3];
                    cmp[2] = cmp[0];
                    cmp[3] = cmp[1];
                    cmp[0] = idx;
                    cmp[1] = end - idx;
                } else if ((end - idx) > cmp[3]) {
                    cmp[4] = cmp[2];
                    cmp[5] = cmp[3];
                    cmp[2] = idx;
                    cmp[3] = end - idx;
                } else if ((end - idx) > cmp[3]) {
                    cmp[4] = idx;
                    cmp[5] = end - idx;
                }
            }
        }

        cmp[1] += cmp[0];
        cmp[3] += cmp[2];
        cmp[5] += cmp[4];
        return cmp;
    }

    /**
     * Search for a string. Use compile_search() to first generate the second
     * argument. This uses a Knuth-Morris-Pratt like algorithm.
     *
     * @param search
     *         the string to search for.
     * @param cmp
     *         the the array returned by compile_search.
     * @param str
     *         the string in which to look for <var>search</var>.
     * @param beg
     *         the position at which to start the search in <var>str</var>.
     * @param end
     *         the position at which to end the search in <var>str</var>,
     *         noninclusive.
     * @return the position in <var>str</var> where <var>search</var> was found,
     *         or -1 if not found.
     */
    final static int findStr(byte[] search, int[] cmp, byte[] str, int beg, int end) {
        int c1f = cmp[0], c1l = cmp[1], d1 = c1l - c1f, c2f = cmp[2], c2l = cmp[3], d2 = c2l - c2f, c3f = cmp[4], c3l =
                cmp[5], d3 = c3l - c3f;

        Find:
        while (beg + search.length <= end) {
            if (search[c1l] == str[beg + c1l]) {
            /*
             * This is correct, but Visual J++ can't cope with it... Comp: if
             * (search[c1f] == str[beg+c1f]) { for (int idx=0; idx<search.length;
             * idx++) if (search[idx] != str[beg+idx]) break Comp; break Find; // we
             * found it } so here is the replacement:
             */
                if (search[c1f] == str[beg + c1f]) {
                    boolean same = true;

                    for (int idx = 0; idx < search.length; idx++)
                        if (search[idx] != str[beg + idx]) {
                            same = false;
                            break;
                        }

                    if (same)
                        break Find; // we found it
                }

                beg += d1;
            } else if (search[c2l] == str[beg + c2l])
                beg += d2;
            else if (search[c3l] == str[beg + c3l])
                beg += d3;
            else
                beg++;
        }

        if (beg + search.length > end)
            return -1;
        else
            return beg;
    }

    /**
     * Replace quoted characters by their unquoted version. Quoted characters are
     * characters preceded by a slash. E.g. "\c" would be replaced by "c". This
     * is used in parsing http headers where quoted-characters are allowed in
     * quoted-strings and often used to quote the quote character &lt;"&gt;.
     *
     * @param str
     *         the string do dequote
     * @return the string do with all quoted characters replaced by their true
     *         value.
     */
    public final static String dequoteString(String str) {
        if (str.indexOf('\\') == -1)
            return str;

        char[] buf = str.toCharArray();
        int pos = 0, num_deq = 0;
        while (pos < buf.length) {
            if (buf[pos] == '\\' && pos + 1 < buf.length) {
                System.arraycopy(buf, pos + 1, buf, pos, buf.length - pos - 1);
                num_deq++;
            }
            pos++;
        }

        return new String(buf, 0, buf.length - num_deq);
    }

    /**
     * Replace given characters by their quoted version. Quoted characters are
     * characters preceded by a slash. E.g. "c" would be replaced by "\c". This
     * is used in generating http headers where certain characters need to be
     * quoted, such as the quote character &lt;"&gt;.
     *
     * @param str
     *         the string do quote
     * @param qlist
     *         the list of characters to quote
     * @return the string do with all characters replaced by their quoted
     *         version.
     */
    public final static String quoteString(String str, String qlist) {
        char[] list = qlist.toCharArray();
        int idx;
        for (idx = 0; idx < list.length; idx++)
            if (str.indexOf(list[idx]) != -1)
                break;
        if (idx == list.length)
            return str;

        int len = str.length();
        char[] buf = new char[len * 2];
        str.getChars(0, len, buf, 0);
        int pos = 0;
        while (pos < len) {
            if (qlist.indexOf(buf[pos], 0) != -1) {
                if (len == buf.length)
                    buf = Util.resizeArray(buf, len + str.length());

                System.arraycopy(buf, pos, buf, pos + 1, len - pos);
                len++;
                buf[pos++] = '\\';
            }
            pos++;
        }

        return new String(buf, 0, len);
    }

    /**
     * This parses the value part of a header. All quoted strings are dequoted.
     *
     * @param header
     *         the value part of the header.
     * @return a Vector containing all the elements; each entry is an instance of
     *         <var>HttpHeaderElement</var>.
     * @throws ParseException
     *         if the syntax rules are violated.
     * @see #parseHeader(java.lang.String, boolean)
     */
    public final static Vector parseHeader(String header) throws ParseException {
        return parseHeader(header, true);
    }

    /**
     * This parses the value part of a header. The result is a Vector of
     * HttpHeaderElement's. The syntax the header must conform to is:
     * <p/>
     * <PRE>
     * header  = [ element ] *( &quot;,&quot; [ element ] )
     * element = name [ &quot;=&quot; [ value ] ] *( &quot;;&quot; [ param ] )
     * param   = name [ &quot;=&quot; [ value ] ]
     * <p/>
     * name    = token
     * value   = ( token | quoted-string )
     * <p/>
     * token         = 1*&lt;any char except &quot;=&quot;, &quot;,&quot;, &quot;;&quot;, &lt;&quot;&gt; and
     * white space&gt;
     * quoted-string = &lt;&quot;&gt; *( text | quoted-char ) &lt;&quot;&gt;
     * text          = any char except &lt;&quot;&gt;
     * quoted-char   = &quot;\&quot; char
     * </PRE>
     * <p/>
     * Any amount of white space is allowed between any part of the header,
     * element or param and is ignored. A missing value in any element or param
     * will be stored as the empty string; if the "=" is also missing
     * <var>null</var> will be stored instead.
     *
     * @param header
     *         the value part of the header.
     * @param dequote
     *         if true all quoted strings are dequoted.
     * @return a Vector containing all the elements; each entry is an instance of
     *         <var>HttpHeaderElement</var>.
     * @throws ParseException
     *         if the above syntax rules are violated.
     * @see HTTPClient.HttpHeaderElement
     */
    public final static Vector parseHeader(String header, boolean dequote) throws ParseException {
        if (header == null)
            return null;
        char[] buf = header.toCharArray();
        Vector elems = new Vector();
        boolean first = true;
        int beg = -1, end = 0, len = buf.length, abeg[] = new int[1];
        String elem_name, elem_value;

        elements:
        while (true) {
            if (!first) // find required ","
            {
                beg = skipSpace(buf, end);
                if (beg == len)
                    break;
                if (buf[beg] != ',')
                    throw new ParseException("Bad header format: '" + header + "'\nExpected \",\" at position " + beg);
            }
            first = false;

            beg = skipSpace(buf, beg + 1);
            if (beg == len)
                break elements;
            if (buf[beg] == ',') // skip empty elements
            {
                end = beg;
                continue elements;
            }

            if (buf[beg] == '=' || buf[beg] == ';' || buf[beg] == '"')
                throw new ParseException("Bad header format: '" + header + "'\nEmpty element name at position " + beg);

            end = beg + 1; // extract element name
            while (end < len && !Character.isWhitespace(buf[end]) && buf[end] != '=' && buf[end] != ',' && buf[end] != ';')
                end++;
            elem_name = new String(buf, beg, end - beg);

            beg = skipSpace(buf, end);
            if (beg < len && buf[beg] == '=') // element value
            {
                abeg[0] = beg + 1;
                elem_value = parseValue(buf, abeg, header, dequote);
                end = abeg[0];
            } else {
                elem_value = null;
                end = beg;
            }

            NVPair[] params = new NVPair[0];
            params:
            while (true) {
                String param_name, param_value;

                beg = skipSpace(buf, end); // expect ";"
                if (beg == len || buf[beg] != ';')
                    break params;

                beg = skipSpace(buf, beg + 1);
                if (beg == len || buf[beg] == ',') {
                    end = beg;
                    break params;
                }
                if (buf[beg] == ';') // skip empty parameters
                {
                    end = beg;
                    continue params;
                }

                if (buf[beg] == '=' || buf[beg] == '"')
                    throw new ParseException("Bad header format: '" + header + "'\nEmpty parameter name at position " + beg);

                end = beg + 1; // extract param name
                while (end < len && !Character.isWhitespace(buf[end]) && buf[end] != '=' && buf[end] != ','
                       && buf[end] != ';')
                    end++;
                param_name = new String(buf, beg, end - beg);

                beg = skipSpace(buf, end);
                if (beg < len && buf[beg] == '=') // element value
                {
                    abeg[0] = beg + 1;
                    param_value = parseValue(buf, abeg, header, dequote);
                    end = abeg[0];
                } else {
                    param_value = null;
                    end = beg;
                }

                params = Util.resizeArray(params, params.length + 1);
                params[params.length - 1] = new NVPair(param_name, param_value);
            }

            elems.addElement(new HttpHeaderElement(elem_name, elem_value, params));
        }

        return elems;
    }

    /** Parse the value part. Accepts either token or quoted string. */
    private static String parseValue(char[] buf, int[] abeg, String header, boolean dequote) throws ParseException {
        int beg = abeg[0], end = beg, len = buf.length;
        String value;

        beg = skipSpace(buf, beg);

        if (beg < len && buf[beg] == '"') // it's a quoted-string
        {
            beg++;
            end = beg;
            char[] deq_buf = null;
            int deq_pos = 0, lst_pos = beg;

            while (end < len && buf[end] != '"') {
                if (buf[end] == '\\') {
                    if (dequote) // dequote char
                    {
                        if (deq_buf == null)
                            deq_buf = new char[buf.length];
                        System.arraycopy(buf, lst_pos, deq_buf, deq_pos, end - lst_pos);
                        deq_pos += end - lst_pos;
                        lst_pos = ++end;
                    } else
                        end++; // skip quoted char
                }

                end++;
            }
            if (end == len)
                throw new ParseException("Bad header format: '" + header + "'\nClosing <\"> for quoted-string"
                                         + " starting at position " + (beg - 1) + " not found");
            if (deq_buf != null) {
                System.arraycopy(buf, lst_pos, deq_buf, deq_pos, end - lst_pos);
                deq_pos += end - lst_pos;
                value = new String(deq_buf, 0, deq_pos);
            } else
                value = new String(buf, beg, end - beg);
            end++;
        } else
        // it's a simple token value
        {
            end = beg;
            while (end < len && !Character.isWhitespace(buf[end]) && buf[end] != ',' && buf[end] != ';')
                end++;

            value = new String(buf, beg, end - beg);
        }

        abeg[0] = end;
        return value;
    }

    /**
     * Determines if the given header contains a certain token. The header must
     * conform to the rules outlined in parseHeader().
     *
     * @param header
     *         the header value.
     * @param token
     *         the token to find; the match is case-insensitive.
     * @return true if the token is present, false otherwise.
     * @throws ParseException
     *         if this is thrown parseHeader().
     * @see #parseHeader(java.lang.String)
     */
    public final static boolean hasToken(String header, String token) throws ParseException {
        if (header == null)
            return false;
        else
            return parseHeader(header).contains(new HttpHeaderElement(token));
    }

    /**
     * Get the HttpHeaderElement with the name <var>name</var>.
     *
     * @param header
     *         a vector of HttpHeaderElement's, such as is returned from
     *         <code>parseHeader()</code>
     * @param name
     *         the name of element to retrieve; matching is case-insensitive
     * @return the request element, or null if none found.
     * @see #parseHeader(java.lang.String)
     */
    public final static HttpHeaderElement getElement(Vector header, String name) {
        int idx = header.indexOf(new HttpHeaderElement(name));
        if (idx == -1)
            return null;
        else
            return (HttpHeaderElement)header.elementAt(idx);
    }

    /**
     * retrieves the value associated with the parameter <var>param</var> in a
     * given header string. It parses the header using <code>parseHeader()</code>
     * and then searches the first element for the given parameter. This is used
     * especially in headers like 'Content-type' and 'Content-Disposition'.
     * <p/>
     * quoted characters ("\x") in a quoted string are dequoted.
     *
     * @param param
     *         the parameter name
     * @param hdr
     *         the header value
     * @return the value for this parameter, or null if not found.
     * @throws ParseException
     *         if the above syntax rules are violated.
     * @see #parseHeader(java.lang.String)
     */
    public final static String getParameter(String param, String hdr) throws ParseException {
        NVPair[] params = ((HttpHeaderElement)parseHeader(hdr).firstElement()).getParams();

        for (int idx = 0; idx < params.length; idx++) {
            if (params[idx].getName().equalsIgnoreCase(param))
                return params[idx].getValue();
        }

        return null;
    }

    /**
     * Assembles a Vector of HttpHeaderElements into a full header string. The
     * individual header elements are seperated by a ", ".
     *
     * @param the
     *         parsed header
     * @return a string containing the assembled header
     */
    public final static String assembleHeader(Vector pheader) {
        StringBuffer hdr = new StringBuffer(200);
        int len = pheader.size();

        for (int idx = 0; idx < len; idx++) {
            ((HttpHeaderElement)pheader.elementAt(idx)).appendTo(hdr);
            hdr.append(", ");
        }
        hdr.setLength(hdr.length() - 2);

        return hdr.toString();
    }

    /**
     * returns the position of the first non-space character in a char array
     * starting a position pos.
     *
     * @param str
     *         the char array
     * @param pos
     *         the position to start looking
     * @return the position of the first non-space character
     */
    final static int skipSpace(char[] str, int pos) {
        int len = str.length;
        while (pos < len && Character.isWhitespace(str[pos]))
            pos++;
        return pos;
    }

    /**
     * returns the position of the first space character in a char array starting
     * a position pos.
     *
     * @param str
     *         the char array
     * @param pos
     *         the position to start looking
     * @return the position of the first space character, or the length of the
     *         string if not found
     */
    final static int findSpace(char[] str, int pos) {
        int len = str.length;
        while (pos < len && !Character.isWhitespace(str[pos]))
            pos++;
        return pos;
    }

    /**
     * returns the position of the first non-token character in a char array
     * starting a position pos.
     *
     * @param str
     *         the char array
     * @param pos
     *         the position to start looking
     * @return the position of the first non-token character, or the length of
     *         the string if not found
     */
    final static int skipToken(char[] str, int pos) {
        int len = str.length;
        while (pos < len && TokenChar.get(str[pos]))
            pos++;
        return pos;
    }

    /**
     * Does the string need to be quoted when sent in a header? I.e. does it
     * contain non-token characters?
     *
     * @param str
     *         the string
     * @return true if it needs quoting (i.e. it contains non-token chars)
     */
    final static boolean needsQuoting(String str) {
        int len = str.length(), pos = 0;

        while (pos < len && TokenChar.get(str.charAt(pos)))
            pos++;
        return (pos < len);
    }

    /**
     * Compares two http urls for equality. This exists because the method
     * <code>java.net.URL.sameFile()</code> is broken (an explicit port 80
     * doesn't compare equal to an implicit port, and it doesn't take escapes
     * into account).
     * <p/>
     * Two http urls are considered equal if they have the same protocol
     * (case-insensitive match), the same host (case-insensitive), the same port
     * and the same file (after decoding escaped characters).
     *
     * @param url1
     *         the first url
     * @param url1
     *         the second url
     * @return true if <var>url1</var> and <var>url2</var> compare equal
     */
    public final static boolean sameHttpURL(URL url1, URL url2) {
        if (!url1.getProtocol().equalsIgnoreCase(url2.getProtocol()))
            return false;

        if (!url1.getHost().equalsIgnoreCase(url2.getHost()))
            return false;

        int port1 = url1.getPort(), port2 = url2.getPort();
        if (port1 == -1)
            port1 = URI.defaultPort(url1.getProtocol());
        if (port2 == -1)
            port2 = URI.defaultPort(url1.getProtocol());
        if (port1 != port2)
            return false;

        try {
            return URI.unescape(url1.getFile(), null).equals(URI.unescape(url2.getFile(), null));
        } catch (ParseException pe) {
            return url1.getFile().equals(url2.getFile());
        }
    }

    /**
     * Return the default port used by a given protocol.
     *
     * @param protocol
     *         the protocol
     * @return the port number, or 0 if unknown
     * @see HTTPClient.URI#defaultPort(java.lang.String)
     * @deprecated use URI.defaultPort() instead
     */
    public final static int defaultPort(String protocol) {
        return URI.defaultPort(protocol);
    }

    /**
     * Parse the http date string. java.util.Date will do this fine, but is
     * deprecated, so we use SimpleDateFormat instead.
     *
     * @param dstr
     *         the date string to parse
     * @return the Date object
     */
    final static Date parseHttpDate(String dstr) {
        synchronized (http_parse_lock) {
            if (parse_1123 == null)
                setupParsers();
        }

        try {
            return parse_1123.parse(dstr);
        } catch (java.text.ParseException pe) {
        }
        try {
            return parse_850.parse(dstr);
        } catch (java.text.ParseException pe) {
        }
        try {
            return parse_asctime.parse(dstr);
        } catch (java.text.ParseException pe) {
            throw new IllegalArgumentException(pe.toString());
        }
    }

    private static final void setupParsers() {
        parse_1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
        parse_850 = new SimpleDateFormat("EEEE, dd-MMM-yy HH:mm:ss 'GMT'", Locale.US);
        parse_asctime = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy", Locale.US);

        parse_1123.setTimeZone(new SimpleTimeZone(0, "GMT"));
        parse_850.setTimeZone(new SimpleTimeZone(0, "GMT"));
        parse_asctime.setTimeZone(new SimpleTimeZone(0, "GMT"));

        parse_1123.setLenient(true);
        parse_850.setLenient(true);
        parse_asctime.setLenient(true);
    }

    /**
     * This returns a string containing the date and time in <var>date</var>
     * formatted according to a subset of RFC-1123. The format is defined in the
     * HTTP/1.0 spec (RFC-1945), section 3.3, and the HTTP/1.1 spec (RFC-2616),
     * section 3.3.1. Note that Date.toGMTString() is close, but is missing the
     * weekday and supresses the leading zero if the day is less than the 10th.
     * Instead we use the SimpleDateFormat class.
     * <p/>
     * Some versions of JDK 1.1.x are bugged in that their GMT uses daylight
     * savings time... Therefore we use our own timezone definitions.
     *
     * @param date
     *         the date and time to be converted
     * @return a string containg the date and time as used in http
     */
    public static final String httpDate(Date date) {
        synchronized (http_format_lock) {
            if (http_format == null)
                setupFormatter();
        }

        return http_format.format(date);
    }

    private static final void setupFormatter() {
        http_format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
        http_format.setTimeZone(new SimpleTimeZone(0, "GMT"));
    }

    /**
     * Escape unsafe characters in a path.
     *
     * @param path
     *         the original path
     * @return the path with all unsafe characters escaped
     */
    final static String escapeUnsafeChars(String path) {
        int len = path.length();
        char[] buf = new char[3 * len];

        int dst = 0;
        for (int src = 0; src < len; src++) {
            char ch = path.charAt(src);
            if (ch >= 128 || UnsafeChar.get(ch)) {
                buf[dst++] = '%';
                buf[dst++] = hex_map[(ch & 0xf0) >>> 4];
                buf[dst++] = hex_map[ch & 0x0f];
            } else
                buf[dst++] = ch;
        }

        if (dst > len)
            return new String(buf, 0, dst);
        else
            return path;
    }

    static final char[] hex_map = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

    /**
     * Extract the path from an http resource.
     * <p/>
     * The "resource" part of an HTTP URI can contain a number of parts, some of
     * which are not always of interest. These methods here will extract the
     * various parts, assuming the following syntanx (taken from RFC-2616):
     * <p/>
     * <PRE>
     * resource = [ &quot;/&quot; ] [ path ] [ &quot;;&quot; params ] [ &quot;?&quot; query ] [ &quot;#&quot; fragment ]
     * </PRE>
     *
     * @param the
     *         resource to split
     * @return the path, including any leading "/"
     * @see #getParams
     * @see #getQuery
     * @see #getFragment
     */
    public final static String getPath(String resource) {
        int p, end = resource.length();
        if ((p = resource.indexOf('#')) != -1) // find fragment
            end = p;
        if ((p = resource.indexOf('?')) != -1 && p < end) // find query
            end = p;
        if ((p = resource.indexOf(';')) != -1 && p < end) // find params
            end = p;
        return resource.substring(0, end);
    }

    /**
     * Extract the params part from an http resource.
     *
     * @param the
     *         resource to split
     * @return the params, or null if there are none
     * @see #getPath
     */
    public final static String getParams(String resource) {
        int beg, f, q;
        if ((beg = resource.indexOf(';')) == -1) // find params
            return null;
        if ((f = resource.indexOf('#')) != -1 && f < beg) // find fragment
            return null;
        if ((q = resource.indexOf('?')) != -1 && q < beg) // find query
            return null;
        if (q == -1 && f == -1)
            return resource.substring(beg + 1);
        if (f == -1 || (q != -1 && q < f))
            return resource.substring(beg + 1, q);
        else
            return resource.substring(beg + 1, f);
    }

    /**
     * Extract the query string from an http resource.
     *
     * @param the
     *         resource to split
     * @return the query, or null if there was none
     * @see #getPath
     */
    public final static String getQuery(String resource) {
        int beg, f;
        if ((beg = resource.indexOf('?')) == -1) // find query
            return null;
        if ((f = resource.indexOf('#')) != -1 && f < beg) // find fragment
            return null; // '?' is in fragment
        if (f == -1)
            return resource.substring(beg + 1); // no fragment
        else
            return resource.substring(beg + 1, f); // strip fragment
    }

    /**
     * Extract the fragment part from an http resource.
     *
     * @param the
     *         resource to split
     * @return the fragment, or null if there was none
     * @see #getPath
     */
    public final static String getFragment(String resource) {
        int beg;
        if ((beg = resource.indexOf('#')) == -1) // find fragment
            return null;
        else
            return resource.substring(beg + 1);
    }

    /**
     * Match <var>pattern</var> against <var>name</var>, where <var>pattern</var>
     * may contain wildcards ('*').
     *
     * @param pattern
     *         the pattern to match; may contain '*' which match any
     *         number (0 or more) of any character (think file globbing)
     * @param name
     *         the name to match against the pattern
     * @return true if the name matches the pattern; false otherwise
     */
    public static final boolean wildcardMatch(String pattern, String name) {
        return wildcardMatch(pattern, name, 0, 0, pattern.length(), name.length());
    }

    private static final boolean wildcardMatch(String pattern, String name, int ppos, int npos, int plen, int nlen) {
        // find wildcard
        int star = pattern.indexOf('*', ppos);
        if (star < 0) {
            return ((plen - ppos) == (nlen - npos) && pattern.regionMatches(ppos, name, npos, plen - ppos));
        }

        // match prefix
        if (!pattern.regionMatches(ppos, name, npos, star - ppos))
            return false;

        // match suffix
        if (star == plen - 1)
            return true;
        while (!wildcardMatch(pattern, name, star + 1, npos, plen, nlen) && npos < nlen)
            npos++;
        return (npos < nlen);
    }
}
